﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Web;
using System.Web.Mvc;
using System.Linq.Expressions;
using System.Web.Routing;
using System.Web.Mvc.Html;
using GSite.Utils.Expressions;

namespace GSite.Utils.Mvc
{
    public static class HtmlExtensions
    {
        public static readonly string CascadingDropDownLoadingText = "зареждане...";
        public static readonly string CascadingDropDownErrorText = "грешка при зареждане.";
        public static readonly string CascadingDropDownParameterName = "parentId";
        public static readonly string CascadingDropDownDisabledCssClassName = "disabled";
        public static readonly string DropDownEmptyItemText = "--Моля, изберете--";

        public static MvcHtmlString GetDisplayName<TModel, TProperty>(this HtmlHelper<TModel> htmlHelper, Expression<Func<TModel, TProperty>> expression)
        {
            return GetDisplayNameInterNal(htmlHelper, expression, false);
        }

        public static MvcHtmlString GetDisplayName<TModel, TProperty>(this HtmlHelper<TModel> htmlHelper, Expression<Func<TModel, TProperty>> expression, bool isVInSpan)
        {
            return GetDisplayNameInterNal(htmlHelper, expression, isVInSpan);
        }

        private static MvcHtmlString GetDisplayNameInterNal<TModel, TProperty>(this HtmlHelper<TModel> htmlHelper, Expression<Func<TModel, TProperty>> expression, bool isInVSpan)
        {
            var metadata = ModelMetadata.FromLambdaExpression(expression, htmlHelper.ViewData);
            string value = metadata.DisplayName ?? (metadata.PropertyName ?? ExpressionHelper.GetExpressionText(expression));

            TagBuilder tag = new TagBuilder("span");
            tag.AddCssClass("margin-span");

            if (isInVSpan)
                tag.AddCssClass("span-top");

            tag.InnerHtml = value;

            return MvcHtmlString.Create(tag.ToString(TagRenderMode.Normal));
        }

        public static MvcHtmlString CheckBoxDisplayNameFor<TModel, TProperty>(this HtmlHelper<TModel> htmlHelper, Expression<Func<TModel, TProperty>> expression)
        {
            return CheckBoxDisplayNameForInternal(htmlHelper, expression, null, null);
        }

        public static MvcHtmlString CheckBoxDisplayNameFor<TModel, TProperty>(this HtmlHelper<TModel> htmlHelper, Expression<Func<TModel, TProperty>> expression, object htmlAttributes)
        {

            return CheckBoxDisplayNameForInternal(htmlHelper, expression, htmlAttributes, null);
        }

        private static MvcHtmlString CheckBoxDisplayNameForInternal<TModel, TProperty>(this HtmlHelper<TModel> htmlHelper, Expression<Func<TModel, TProperty>> expression, object htmlAttributes, string checkedValue)
        {

            ModelMetadata metadata = ModelMetadata.FromLambdaExpression(expression, htmlHelper.ViewData);
            string htmlFieldName = ExpressionHelper.GetExpressionText(expression);

            TagBuilder tag = new TagBuilder("input");
            tag.Attributes.Add("type", "checkbox");
            tag.Attributes.Add("name", metadata.PropertyName);
            if (!string.IsNullOrEmpty(checkedValue))
            {
                tag.Attributes.Add("value", checkedValue);
            }
            else
            {
                tag.Attributes.Add("value", metadata.Model.ToString());
            }

            if (htmlAttributes != null)
            {
                tag.MergeAttributes(new RouteValueDictionary(htmlAttributes));
            }

            if (metadata.Model.ToString().ToLower() == "true")
            {
                tag.Attributes.Add("checked", "checked");
            }

            string value = metadata.DisplayName ?? (metadata.PropertyName ?? ExpressionHelper.GetExpressionText(expression));

            return MvcHtmlString.Create(tag.ToString(TagRenderMode.Normal) + value);
        }

        #region AutoCompleteTextBoxFor

        public static MvcHtmlString AutoCompleteTextBoxFor<TModel, TValue, TId>(this HtmlHelper<TModel> htmlHelper,
            Expression<Func<TModel, TValue>> valueFieldExpression, Expression<Func<TModel, TId>> idFieldExpression,
            string url, Expression<Func<TModel, object>> parentFieldExpression = null, object htmlAttributes = null,
            bool enableQuickSearch = false, object additionalOptions = null,
            Expression<Func<TModel, object>>[] additionalParentFieldExpressions = null)
        {
            return AutoCompleteTextBoxHelper(htmlHelper, valueFieldExpression, idFieldExpression, parentFieldExpression, url,
                new RouteValueDictionary(htmlAttributes), enableQuickSearch, new RouteValueDictionary(additionalOptions),
                additionalParentFieldExpressions ?? new Expression<Func<TModel, object>>[0]);
        }

        private static MvcHtmlString AutoCompleteTextBoxHelper<TModel, TValue, TId>(this HtmlHelper<TModel> htmlHelper,
            Expression<Func<TModel, TValue>> valueFieldExpression, Expression<Func<TModel, TId>> idFieldExpression,
            Expression<Func<TModel, object>> parentFieldExpression, string url, IDictionary<string, object> htmlAttributes,
            bool enableQuickSearch, IDictionary<string, object> additionalOptions,
            Expression<Func<TModel, object>>[] additionalParentFieldExpressions)
        {
            string valueFieldId = ExpressionTextHelper.GetExpressionText(valueFieldExpression);
            object valueFieldValue = ModelMetadata.FromLambdaExpression(valueFieldExpression, htmlHelper.ViewData).Model;

            string idFieldId = ExpressionTextHelper.GetExpressionText(idFieldExpression);
            object idFieldValue = ModelMetadata.FromLambdaExpression(idFieldExpression, htmlHelper.ViewData).Model;

            string textBoxId = valueFieldId + "TextBox";

            string parentFieldId = parentFieldExpression != null ? ExpressionTextHelper.GetExpressionText(parentFieldExpression) : null;
            string[] additionalParentFieldIds = additionalParentFieldExpressions.Select(e => e != null ? ExpressionTextHelper.GetExpressionText(e) : null).ToArray();

            string[] optionsArray = additionalOptions
                .Select(kvp => String.Format("{0}:{1}", kvp.Key, kvp.Value))
                .ToArray();

            string initScript = String.Format(
            "<script type=\"text/javascript\">\n" +
            "    $.createAutoComplete({0}, {1}, {2}, {3}, {4}, {5}, [{6}], {{{7}}});\n" +
            "</script>", ToJsString(url), ToJsString(textBoxId), ToJsString(valueFieldId),
                ToJsString(idFieldId), ToJsString(parentFieldId), enableQuickSearch.ToString().ToLowerInvariant(),
                String.Join(",", additionalParentFieldIds.Select(s => ToJsString(s))), String.Join(",", optionsArray));

            string textBoxElement = htmlHelper.TextBox(textBoxId, valueFieldValue, htmlAttributes).ToHtmlString();
            string hiddenValueElement = htmlHelper.Hidden(valueFieldId, valueFieldValue).ToHtmlString();
            string hiddenIdElement = htmlHelper.Hidden(idFieldId, idFieldValue).ToHtmlString();

            return MvcHtmlString.Create(String.Format("{0}\n{1}\n{2}\n{3}", hiddenIdElement, hiddenValueElement, textBoxElement, initScript));
        }

        #endregion

        #region AutoCompleteTextBoxFor with Description

        public static MvcHtmlString AutoCompleteDescriptiveTextBoxFor<TModel, TValue, TId>(this HtmlHelper<TModel> htmlHelper,
           Expression<Func<TModel, TValue>> valueFieldExpression, Expression<Func<TModel, TId>> idFieldExpression,
           Expression<Func<TModel, TValue>> descriptionFieldExpression,
           string url, object htmlAttributes = null, bool enableQuickSearch = false, object additionalOptions = null)
        {
            return AutoCompleteDescriptiveTextBoxHelper(htmlHelper, valueFieldExpression, idFieldExpression, descriptionFieldExpression, 
                url, new RouteValueDictionary(htmlAttributes), enableQuickSearch, new RouteValueDictionary(additionalOptions));
        }

        private static MvcHtmlString AutoCompleteDescriptiveTextBoxHelper<TModel, TValue, TId>(this HtmlHelper<TModel> htmlHelper,
            Expression<Func<TModel, TValue>> valueFieldExpression, Expression<Func<TModel, TId>> idFieldExpression,
            Expression<Func<TModel, TValue>> descriptionFieldExpression,
            string url, IDictionary<string, object> htmlAttributes,
            bool enableQuickSearch, IDictionary<string, object> additionalOptions)
        {
            string valueFieldId = ExpressionTextHelper.GetExpressionText(valueFieldExpression);
            object valueFieldValue = ModelMetadata.FromLambdaExpression(valueFieldExpression, htmlHelper.ViewData).Model;

            string idFieldId = ExpressionTextHelper.GetExpressionText(idFieldExpression);
            object idFieldValue = ModelMetadata.FromLambdaExpression(idFieldExpression, htmlHelper.ViewData).Model;

            string desctiptionFieldId = ExpressionTextHelper.GetExpressionText(descriptionFieldExpression);
            object desctiptionFieldValue = ModelMetadata.FromLambdaExpression(descriptionFieldExpression, htmlHelper.ViewData).Model;

            string textBoxId = valueFieldId + "TextBox";

            
            

            string[] optionsArray = additionalOptions
                .Select(kvp => String.Format("{0}:{1}", kvp.Key, kvp.Value))
                .ToArray();

            string initScript = String.Format(
            "<script type=\"text/javascript\">\n" +
            "    $.createDescriptiveAutoComplete({0}, {1}, {2}, {3}, {4}, {5}, {{{6}}});\n" +
            "</script>", 
            ToJsString(url), 
            ToJsString(textBoxId), 
            ToJsString(valueFieldId),
            ToJsString(idFieldId),
            ToJsString(desctiptionFieldId), 
            enableQuickSearch.ToString().ToLowerInvariant(),            
            String.Join(",", optionsArray));

            string textBoxElement = htmlHelper.TextBox(textBoxId, valueFieldValue, htmlAttributes).ToHtmlString();
            string hiddenValueElement = htmlHelper.Hidden(valueFieldId, valueFieldValue).ToHtmlString();
            string hiddenIdElement = htmlHelper.Hidden(idFieldId, idFieldValue).ToHtmlString();

            return MvcHtmlString.Create(String.Format("{0}\n{1}\n{2}\n{3}", hiddenIdElement, hiddenValueElement, textBoxElement, initScript));
        }

        #endregion

        //#region AutoCompleteTextBoxFor

        //// This overload is used with the T4MVC and does not compile without it
        //// you can remove it if you are not using T4MVC
        ///// <summary>
        ///// Автоматична допълваща контрола
        ///// </summary>
        ///// <typeparam name="TModel">модел</typeparam>
        ///// <typeparam name="TCode">код</typeparam>
        ///// <typeparam name="TName">име</typeparam>
        ///// <param name="htmlHelper">текущ модул</param>
        ///// <param name="codeFieldExpression">полето код</param>
        ///// <param name="nameFieldExpression">полето име</param>
        ///// <param name="result">резултат</param>
        ///// <param name="parentFieldExpression">полето контейнер</param>
        ///// <param name="htmlAttributes">атрибути</param>
        ///// <param name="additionalOptions">допълнителни настройки</param>
        ///// <returns>контрола</returns>
        //public static MvcHtmlString AutoCompleteTextBoxFor<TModel, TCode, TName>(this HtmlHelper<TModel> htmlHelper,
        //    Expression<Func<TModel, TCode>> codeFieldExpression, Expression<Func<TModel, TName>> nameFieldExpression,
        //    ActionResult result, Expression<Func<TModel, object>> parentFieldExpression = null,
        //    object htmlAttributes = null, object additionalOptions = null)
        //{
        //    UrlHelper urlHelper = new UrlHelper(htmlHelper.ViewContext.RequestContext);
        //    string url = urlHelper.RouteUrl(result.GetRouteValueDictionary());
        //    //string url = urlHelper.Action(actionName, controllerName, routeValues);

        //    return AutoCompleteTextBoxHelper(htmlHelper, codeFieldExpression, nameFieldExpression, parentFieldExpression, url,
        //        new RouteValueDictionary(htmlAttributes), new RouteValueDictionary(additionalOptions));
        //}
        ///// <summary>
        ///// Автоматична допълваща контрола
        ///// </summary>
        ///// <typeparam name="TModel">модел</typeparam>
        ///// <typeparam name="TCode">код</typeparam>
        ///// <typeparam name="TName">име</typeparam>
        ///// <param name="htmlHelper">текущ модул</param>
        ///// <param name="codeFieldExpression">полето код</param>
        ///// <param name="nameFieldExpression">полето име</param>
        ///// <param name="actionName">action</param>
        ///// <param name="result">резултат</param>
        ///// <param name="parentFieldExpression">полето контейнер</param>
        ///// <param name="htmlAttributes">атрибути</param>
        ///// <param name="additionalOptions">допълнителни настройки</param>
        ///// <returns>контрола</returns>
        //public static MvcHtmlString AutoCompleteTextBoxFor<TModel, TCode, TName>(this HtmlHelper<TModel> htmlHelper,
        //    Expression<Func<TModel, TCode>> codeFieldExpression, Expression<Func<TModel, TName>> nameFieldExpression,
        //    string actionName, string controllerName = null, object routeValues = null,
        //    Expression<Func<TModel, object>> parentFieldExpression = null, object htmlAttributes = null,
        //    object additionalOptions = null)
        //{
        //    UrlHelper urlHelper = new UrlHelper(htmlHelper.ViewContext.RequestContext);
        //    string url = urlHelper.Action(actionName, controllerName, routeValues);

        //    return AutoCompleteTextBoxHelper(htmlHelper, codeFieldExpression, nameFieldExpression, parentFieldExpression, url,
        //        new RouteValueDictionary(htmlAttributes), new RouteValueDictionary(additionalOptions));
        //}
        ///// <summary>
        ///// Автоматична допълваща контрола
        ///// </summary>
        ///// <typeparam name="TModel">модел</typeparam>
        ///// <typeparam name="TCode">код</typeparam>
        ///// <typeparam name="TName">име</typeparam>
        ///// <param name="htmlHelper">текущ модул</param>
        ///// <param name="codeFieldExpression">полето код</param>
        ///// <param name="nameFieldExpression">полето име</param>
        ///// <param name="result">резултат</param>
        ///// <param name="parentFieldExpression">полето контейнер</param>
        ///// <param name="htmlAttributes">атрибути</param>
        ///// <param name="additionalOptions">допълнителни настройки</param>
        ///// <returns>контрола</returns>
        //private static MvcHtmlString AutoCompleteTextBoxHelper<TModel, TCode, TName>(this HtmlHelper<TModel> htmlHelper,
        //    Expression<Func<TModel, TCode>> codeFieldExpression, Expression<Func<TModel, TName>> nameFieldExpression,
        //    Expression<Func<TModel, object>> parentFieldExpression,
        //    string url, IDictionary<string, object> htmlAttributes, IDictionary<string, object> additionalOptions)
        //{
        //    string nameFieldExpr = ExpressionHelper.GetExpressionText(nameFieldExpression);
        //    string nameFieldId = IdFixer.FixId(htmlHelper.ViewData.TemplateInfo.GetFullHtmlFieldId(nameFieldExpr));

        //    object nameFieldValue = ModelMetadata.FromLambdaExpression(nameFieldExpression, htmlHelper.ViewData).Model;

        //    string codeFieldExpr = ExpressionHelper.GetExpressionText(codeFieldExpression);
        //    string codeFieldId = IdFixer.FixId(htmlHelper.ViewData.TemplateInfo.GetFullHtmlFieldId(codeFieldExpr));
        //    object codeFieldValue = ModelMetadata.FromLambdaExpression(codeFieldExpression, htmlHelper.ViewData).Model;

        //    string textBoxExpr = nameFieldExpr + "TextBox";
        //    string textBoxId = IdFixer.FixId(htmlHelper.ViewData.TemplateInfo.GetFullHtmlFieldId(textBoxExpr));

        //    string parentFieldExpr = parentFieldExpression != null ? ExpressionHelper.GetExpressionText(parentFieldExpression) : null;
        //    string parentFieldId = parentFieldExpr != null ? IdFixer.FixId(htmlHelper.ViewData.TemplateInfo.GetFullHtmlFieldId(parentFieldExpr)) : null;

        //    string[] optionsArray = additionalOptions
        //        .Select(kvp => String.Format("{0}:{1}", kvp.Key, kvp.Value))
        //        .ToArray();

        //    string initScript = String.Format(
        //    "<script type=\"text/javascript\">\n" +
        //    "    createAutoComplete({0}, {1}, {2}, {3}, {4}, {{{5}}});\n" +
        //    "</script>", ToJsString(url), ToJsString(textBoxId), ToJsString(codeFieldId),
        //        ToJsString(nameFieldId), ToJsString(parentFieldId), String.Join(",", optionsArray));

        //    string textBoxElement = htmlHelper.TextBox(textBoxExpr, nameFieldValue, htmlAttributes).ToHtmlString();
        //    string hiddenValueElement = htmlHelper.Hidden(nameFieldExpr, nameFieldValue).ToHtmlString();
        //    string hiddenIdElement = htmlHelper.Hidden(codeFieldExpr, codeFieldValue).ToHtmlString();

        //    return MvcHtmlString.Create(String.Format("{0}\n{1}\n{2}\n{3}", hiddenIdElement, hiddenValueElement, textBoxElement, initScript));
        //}

        //#endregion

        #region CascadingDropDownFor

        // This overload is used with the T4MVC and does not compile without it
        // you can remove it if you are not using T4MVC
        /// <summary>
        /// Автоматична допълваща контрола
        /// </summary>
        /// <typeparam name="TModel">модел</typeparam>
        /// <typeparam name="TCode">код</typeparam>
        /// <typeparam name="TName">име</typeparam>
        /// <typeparam name="TParent">ралативност</typeparam>
        /// <param name="htmlHelper">текущ модул</param>
        /// <param name="codeFieldExpression">полето код</param>
        /// <param name="nameFieldExpression">полето име</param>
        /// <param name="parentFieldExpression">полето релативност</param>
        /// <param name="initValuesFunc">източник на данните</param>
        /// <param name="result">резулатат</param>
        /// <param name="htmlAttributes">атрибути</param>
        /// <param name="additionalOptions">допълнителни настройки</param>
        /// <returns>контрола</returns>
        public static MvcHtmlString CascadingDropDownFor<TModel, TCode, TName, TParent>(this HtmlHelper<TModel> htmlHelper,
            Expression<Func<TModel, TCode>> codeFieldExpression, Expression<Func<TModel, TName>> nameFieldExpression,
            Expression<Func<TModel, TParent>> parentFieldExpression,
            Func<TParent, IEnumerable<SelectListItem>> initValuesFunc, string url, object htmlAttributes = null,
            object additionalOptions = null)
        {
            return CascadingDropDownForHelper(htmlHelper, codeFieldExpression, nameFieldExpression, parentFieldExpression, initValuesFunc,
                url, new RouteValueDictionary(htmlAttributes), new RouteValueDictionary(additionalOptions));
        }

        /// <summary>
        /// Автоматична допълваща контрола
        /// </summary>
        /// <typeparam name="TModel">модел</typeparam>
        /// <typeparam name="TCode">код</typeparam>
        /// <typeparam name="TName">име</typeparam>
        /// <typeparam name="TParent">ралативност</typeparam>
        /// <param name="htmlHelper">текущ модул</param>
        /// <param name="codeFieldExpression">полето код</param>
        /// <param name="nameFieldExpression">полето име</param>
        /// <param name="parentFieldExpression">полето релативност</param>
        /// <param name="initValuesFunc">източник на данните</param>
        /// <param name="actionName">име на страницата</param>
        /// <param name="controllerName">име на конторлата</param>
        /// <param name="routeValues">допълнителни прикачени данни</param>
        /// <param name="htmlAttributes">атрибути</param>
        /// <param name="additionalOptions">допълнителни настройки</param>
        /// <returns>контрола</returns>
        public static MvcHtmlString CascadingDropDownFor<TModel, TCode, TName, TParent>(this HtmlHelper<TModel> htmlHelper,
            Expression<Func<TModel, TCode>> codeFieldExpression, Expression<Func<TModel, TName>> nameFieldExpression,
            Expression<Func<TModel, TParent>> parentFieldExpression,
            Func<TParent, IEnumerable<SelectListItem>> initValuesFunc, string actionName, string controllerName = null,
            object routeValues = null, object htmlAttributes = null, object additionalOptions = null)
        {
            UrlHelper urlHelper = new UrlHelper(htmlHelper.ViewContext.RequestContext);
            string url = urlHelper.Action(actionName, controllerName, routeValues);

            return CascadingDropDownForHelper(htmlHelper, codeFieldExpression, nameFieldExpression, parentFieldExpression, initValuesFunc,
                url, new RouteValueDictionary(htmlAttributes), new RouteValueDictionary(additionalOptions));
        }

        /// <summary>
        /// Автоматична допълваща контрола
        /// </summary>
        /// <typeparam name="TModel">модел</typeparam>
        /// <typeparam name="TCode">код</typeparam>
        /// <typeparam name="TName">име</typeparam>
        /// <typeparam name="TParent">ралативност</typeparam>
        /// <param name="htmlHelper">текущ модул</param>
        /// <param name="codeFieldExpression">полето код</param>
        /// <param name="nameFieldExpression">полето име</param>
        /// <param name="parentFieldExpression">полето релативност</param>
        /// <param name="initValuesFunc">източник на данните</param>
        /// <param name="url">връзка</param>
        /// <param name="htmlAttributes">атрибути</param>
        /// <param name="additionalOptions">допълнителни настройки</param>
        /// <returns>контрола</returns>
        private static MvcHtmlString CascadingDropDownForHelper<TModel, TCode, TName, TParent>(this HtmlHelper<TModel> htmlHelper,
            Expression<Func<TModel, TCode>> codeFieldExpression, Expression<Func<TModel, TName>> nameFieldExpression,
            Expression<Func<TModel, TParent>> parentFieldExpression,
            Func<TParent, IEnumerable<SelectListItem>> initValuesFunc, string url, IDictionary<string, object> htmlAttributes,
            IDictionary<string, object> additionalOptions)
        {
            string nameFieldExpr = ExpressionHelper.GetExpressionText(nameFieldExpression);
            string nameFieldId = IdFixer.FixId(htmlHelper.ViewData.TemplateInfo.GetFullHtmlFieldId(nameFieldExpr));
            object nameFieldValue = ModelMetadata.FromLambdaExpression(nameFieldExpression, htmlHelper.ViewData).Model;

            string codeFieldExpr = ExpressionHelper.GetExpressionText(codeFieldExpression);
            string codeFieldId = IdFixer.FixId(htmlHelper.ViewData.TemplateInfo.GetFullHtmlFieldId(codeFieldExpr));
            object codeFieldValue = ModelMetadata.FromLambdaExpression(codeFieldExpression, htmlHelper.ViewData).Model;

            string parentFieldExpr = ExpressionHelper.GetExpressionText(parentFieldExpression);
            string parentFieldId = IdFixer.FixId(htmlHelper.ViewData.TemplateInfo.GetFullHtmlFieldId(parentFieldExpr));
            object parentFieldValue = ModelMetadata.FromLambdaExpression(parentFieldExpression, htmlHelper.ViewData).Model;

            List<SelectListItem> initialItems = new List<SelectListItem>();

            if (parentFieldValue != null)
            {
                initialItems.AddRange(initValuesFunc((TParent)parentFieldValue));

                if (codeFieldValue != null)
                {
                    foreach (var item in initialItems)
                    {
                        if (item.Value == codeFieldValue.ToString())
                        {
                            item.Selected = true;
                            break;
                        }
                    }
                }
            }

            additionalOptions.Add("loadingText", "'" + CascadingDropDownLoadingText + "'");
            additionalOptions.Add("errorText", "'" + CascadingDropDownErrorText + "'");
            additionalOptions.Add("parameterName", "'" + CascadingDropDownParameterName + "'");
            additionalOptions.Add("disabledClass", "'" + CascadingDropDownDisabledCssClassName + "'");

            string[] optionsArray = additionalOptions
                .Select(kvp => String.Format("{0}:{1}", kvp.Key, kvp.Value))
                .ToArray();

            string initScript = String.Format(
            "<script type=\"text/javascript\">\n" +
            "    createCascadingDropDown('{0}', '{1}', '{2}', '{3}', {{{4}}});\n" +
            "</script>", codeFieldId, nameFieldId, parentFieldId, url, String.Join(",", optionsArray));

            string dropDownElement = htmlHelper.DropDownList(codeFieldExpr, initialItems, htmlAttributes).ToHtmlString();
            string hiddenElement = htmlHelper.Hidden(nameFieldExpr, nameFieldValue).ToHtmlString();

            return MvcHtmlString.Create(String.Format("{0}\n{1}\n{2}", hiddenElement, dropDownElement, initScript));
        }

        #endregion

        #region DropDownListWithHiddenFor
        /// <summary>
        /// Падащ списък със скрито поле за име
        /// </summary>
        /// <typeparam name="TModel">модел</typeparam>
        /// <typeparam name="TCode">код</typeparam>
        /// <typeparam name="TName">име</typeparam>
        /// <param name="htmlHelper">текущ модел</param>
        /// <param name="codeFieldExpression">плето код</param>
        /// <param name="nameFieldExpression">полето име</param>
        /// <param name="initValues">източник на данни</param>
        /// <param name="htmlAttributes">атрибути</param>
        /// <returns>контрола</returns>
        public static MvcHtmlString DropDownListWithHiddenFor<TModel, TCode, TName>(this HtmlHelper<TModel> htmlHelper,
            Expression<Func<TModel, TCode>> codeFieldExpression, Expression<Func<TModel, TName>> nameFieldExpression,
            IEnumerable<SelectListItem> initValues, object htmlAttributes = null)
        {
            return DropDownListWithHiddenFor(htmlHelper, codeFieldExpression, nameFieldExpression, initValues, new RouteValueDictionary(htmlAttributes));
        }
        /// <summary>
        /// Падащ списък със скрито поле за име
        /// </summary>
        /// <typeparam name="TModel">модел</typeparam>
        /// <typeparam name="TCode">код</typeparam>
        /// <typeparam name="TName">име</typeparam>
        /// <param name="htmlHelper">текущ модел</param>
        /// <param name="codeFieldExpression">плето код</param>
        /// <param name="nameFieldExpression">полето име</param>
        /// <param name="initValues">източник на данни</param>
        /// <param name="htmlAttributes">атрибути</param>
        /// <returns>контрола</returns>
        private static MvcHtmlString DropDownListWithHiddenFor<TModel, TCode, TName>(this HtmlHelper<TModel> htmlHelper,
            Expression<Func<TModel, TCode>> codeFieldExpression, Expression<Func<TModel, TName>> nameFieldExpression,
            IEnumerable<SelectListItem> initValues, IDictionary<string, object> htmlAttributes)
        {
            string nameFieldExpr = ExpressionHelper.GetExpressionText(nameFieldExpression);
            string nameFieldId = IdFixer.FixId(htmlHelper.ViewData.TemplateInfo.GetFullHtmlFieldId(nameFieldExpr));
            object nameFieldValue = ModelMetadata.FromLambdaExpression(nameFieldExpression, htmlHelper.ViewData).Model;

            string codeFieldExpr = ExpressionHelper.GetExpressionText(codeFieldExpression);
            string codeFieldId = IdFixer.FixId(htmlHelper.ViewData.TemplateInfo.GetFullHtmlFieldId(codeFieldExpr));
            object codeFieldValue = ModelMetadata.FromLambdaExpression(codeFieldExpression, htmlHelper.ViewData).Model;

            List<SelectListItem> initialItems = new List<SelectListItem>();
            initialItems.AddRange(initValues);

            if (codeFieldValue != null)
            {
                string codeFieldValueString = codeFieldValue.ToString();

                foreach (var item in initialItems)
                {
                    if (item.Value == codeFieldValueString)
                    {
                        item.Selected = true;
                        break;
                    }
                }
            }

            string initScript = String.Format(
            "<script type=\"text/javascript\">\n" +
            "    bindField('{0}', '{1}');\n" +
            "</script>", codeFieldId, nameFieldId);

            string dropDownElement = htmlHelper.DropDownList(codeFieldExpr, initialItems, null, htmlAttributes).ToHtmlString();
            string hiddenElement = htmlHelper.Hidden(nameFieldExpr, (codeFieldValue == null) ? string.Empty : nameFieldValue).ToHtmlString();

            return MvcHtmlString.Create(String.Format("{0}\n{1}\n{2}", dropDownElement, hiddenElement, initScript));
        }


        /// <summary>
        /// Падащ списък със скрито поле за име
        /// </summary>
        /// <typeparam name="TModel">модел</typeparam>
        /// <typeparam name="TCode">код</typeparam>
        /// <param name="htmlHelper">текущ модел</param>
        /// <param name="codeFieldExpression">плето код</param>
        /// <param name="initValues">източник на данни</param>
        /// <param name="htmlAttributes">атрибути</param>
        /// <returns>контрола</returns>
        public static MvcHtmlString DropDownListWithHiddenFor<TModel, TCode>(this HtmlHelper<TModel> htmlHelper,
            Expression<Func<TModel, TCode>> codeFieldExpression, IEnumerable<SelectListItem> initValues, object htmlAttributes = null)
        {
            return DropDownListWithHiddenFor(htmlHelper, codeFieldExpression, initValues, new RouteValueDictionary(htmlAttributes));
        }
        /// <summary>
        /// Падащ списък със скрито поле за име
        /// </summary>
        /// <typeparam name="TModel">модел</typeparam>
        /// <typeparam name="TCode">код</typeparam>
        /// <param name="htmlHelper">текущ модел</param>
        /// <param name="codeFieldExpression">плето код</param>
        /// <param name="initValues">източник на данни</param>
        /// <param name="htmlAttributes">атрибути</param>
        /// <returns>контрола</returns>
        private static MvcHtmlString DropDownListWithHiddenFor<TModel, TCode>(this HtmlHelper<TModel> htmlHelper,
            Expression<Func<TModel, TCode>> codeFieldExpression, IEnumerable<SelectListItem> initValues, IDictionary<string, object> htmlAttributes)
        {
            string codeFieldExpr = ExpressionHelper.GetExpressionText(codeFieldExpression);
            string codeFieldId = IdFixer.FixId(htmlHelper.ViewData.TemplateInfo.GetFullHtmlFieldId(codeFieldExpr));
            object codeFieldValue = ModelMetadata.FromLambdaExpression(codeFieldExpression, htmlHelper.ViewData).Model;

            List<SelectListItem> initialItems = new List<SelectListItem>();
            initialItems.AddRange(initValues);

            if (codeFieldValue != null)
            {
                string codeFieldValueString = codeFieldValue.ToString();

                foreach (var item in initialItems)
                {
                    if (item.Value == codeFieldValueString)
                    {
                        item.Selected = true;
                        break;
                    }
                }
            }

            string dropDownElement = htmlHelper.DropDownList(codeFieldExpr, initialItems, null, htmlAttributes).ToHtmlString();

            return MvcHtmlString.Create(String.Format("{0}", dropDownElement));
        }
        #endregion

        #region Private Utility Methods

        private static string ToJsString(string s)
        {
            return s != null ? "'" + s + "'" : "undefined";
        }

        #endregion
    }

    //NEEDED BECAUSE OF BUG IN GetFullHtmlFieldId
    /// <summary>
    /// Фиксатор на идентифкатори
    /// </summary>
    public static class IdFixer
    {
        /// <summary>
        /// Връща javascript масив
        /// </summary>
        /// <param name="htmlHelper"></param>
        /// <param name="values">стойности</param>
        /// <param name="varName">име на масива</param>
        /// <returns></returns>
        public static string JavaScriptArray(this HtmlHelper htmlHelper, IList<string> values, string varName)
        {
            StringBuilder sb = new StringBuilder();
            sb.AppendFormat("var {0} = {1};", varName, new System.Web.Script.Serialization.JavaScriptSerializer().Serialize(values));

            return sb.ToString();
        }

        /// <summary>
        /// Фиксатор
        /// </summary>
        /// <param name="originalId">оригинален идентификатор</param>
        /// <returns>фикснат идентификатор</returns>
        public static string FixId(string originalId)
        {
            return CreateSanitizedId(originalId, HtmlHelper.IdAttributeDotReplacement);
        }

        //IdENTICAL WITH System.Web.Mvc.TagBuilder.CreateSanitizedId
        /// <summary>
        /// Създава санитарен идентификатор
        /// </summary>
        /// <param name="originalId">оригинален идентификатор</param>
        /// <param name="dotReplacement">точка заместител</param>
        /// <returns>фикснат идентификатор</returns>
        private static string CreateSanitizedId(string originalId, string dotReplacement)
        {
            if (String.IsNullOrEmpty(originalId))
            {
                return null;
            }

            char firstChar = originalId[0];
            if (!Html401IdUtil.IsLetter(firstChar))
            {
                // the first character must be a letter
                return null;
            }

            StringBuilder sb = new StringBuilder(originalId.Length);
            sb.Append(firstChar);

            for (int i = 1; i < originalId.Length; i++)
            {
                char thisChar = originalId[i];
                if (Html401IdUtil.IsValidIdCharacter(thisChar))
                {
                    sb.Append(thisChar);
                }
                else
                {
                    sb.Append(dotReplacement);
                }
            }

            return sb.ToString();
        }

        //IdENTICAL WITH System.Web.Mvc.TagBuilder.Html401IdUtil
        private static class Html401IdUtil
        {
            private static bool IsAllowableSpecialCharacter(char c)
            {
                switch (c)
                {
                    case '-':
                    case '_':
                    case ':':
                        // note that we're specifically excluding the '.' character
                        return true;

                    default:
                        return false;
                }
            }

            private static bool IsDigit(char c)
            {
                return ('0' <= c && c <= '9');
            }

            public static bool IsLetter(char c)
            {
                return (('A' <= c && c <= 'Z') || ('a' <= c && c <= 'z'));
            }

            public static bool IsValidIdCharacter(char c)
            {
                return (IsLetter(c) || IsDigit(c) || IsAllowableSpecialCharacter(c));
            }
        }
    }
}
